// Copyright (C) 2022 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
//
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<广西木子科技有限公司>，授权编号：<NT220402151538>，代码指纹：<A.648883738.885>，技术对接人微信：<ID: Lingmuziyi>】
// 【授权寄送：<收件：李先生、地址：南宁市科园西十路11号国电智能大厦1101F、电话：17736659550、邮箱：yingshashou@vip.qq.com>】
//
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
//
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.eva.framework.dbpool;

import java.sql.SQLException;
import java.sql.SQLWarning;

import com.eva.epc.common.util.CommonUtils;
import com.eva.epc.common.util.EException;

public class DBUtils {
    /**
     * 据各参数组织成一个Select语句.
     *
     * @param table   要查询的数据库表名（可以是诸如a join b on a.xx=b.xx等组合）
     *                ，可无表名(如调用函数时就不需要)
     * @param columns 要查询的列（多列之间用逗号分隔，形如：f1,f2,f3）
     * @param where   where条件（可无）
     * @return
     */
    public static String formatGetComboItemsSql(String table, String columns, String where) {
        return formatGetComboItemsSql(table, columns, where, null);
    }

    /**
     * 据各参数组织成一个Select语句.
     *
     * @param table   要查询的数据库表名（可以是诸如a join b on a.xx=b.xx等组合）
     *                ，可无表名(如调用函数时就不需要)
     * @param columns 要查询的列（多列之间用逗号分隔，形如：f1,f2,f3）
     * @param where   where条件（可无）
     * @param other   额外的SQL，如可以是order by someColumn或group by someColumn等
     * @return
     */
    public static String formatGetComboItemsSql(String table, String columns
            , String where, String other) {
        StringBuffer sql = new StringBuffer("SELECT ")
                .append(columns);
        //可无表名(如调用函数时就不需要)
        if (!CommonUtils.isStringEmpty(table))
            sql.append(" FROM ").append(table);
//		if(where != null && where.length() > 0)
        if (!CommonUtils.isStringEmpty(where, true))
            sql.append(" WHERE ").append(where).append("  ");
        if (!CommonUtils.isStringEmpty(other, true))
            sql.append(other);
        return sql.toString();
    }

    /**
     * <p>
     * 解析SQLException并组织成EException实例返回.<br>
     * <b>注：</b>在解析每条异常的message时，可能要去掉标识，标识的长度由 {@link DBShell#dbExceptionIdentStringLen}指明.<br>
     * 数据库异常头标记长度，一般都是固定长度，这些标记在此处被认为是多余的，每条都有，形如：
     * “ORA-00001”中的"ORA-"或[Miscrosoft SQL Server ...]......"的”[Miscrosoft SQL Server ...]“这一
     * 段一长度，在读取异常message时，就会把这个长度之前截断，因为它对最晒图用户而言不友好.
     * </p>
     *
     * @param ex 原始SQLException对象，可能包含多条异常信息，见方法 {@link SQLException#getNextException()}
     * @return 封装了一个短异常描述消息和详细描述消息的EException对象(是否加入详细描述由参数needDetail指明)
     * @see EException
     * @see DBShell#needParseNextExceptionsForSQLException
     * @see DBShell#dbExceptionIdentStringLen
     */
    public static EException getErrorMessage(SQLException ex
//			,boolean needDetail
    ) {
        String shortMessage = CommonUtils.isStringEmpty(ex.getLocalizedMessage())
                ? ex.getMessage() : ex.getLocalizedMessage();
        String detailMessage = null;
        int i = DBShell.dbExceptionIdentStringLen;
        if (i > 0)
            shortMessage = shortMessage.substring(i);
        if (DBShell.needParseNextExceptionsForSQLException) {
            for (SQLException next = ex.getNextException();
                 next != null && !(next instanceof SQLWarning);
                 next = next.getNextException()) {
                String nextMsg = next.getLocalizedMessage();
                if (i > 0)
                    nextMsg = nextMsg.substring(i);
                detailMessage = detailMessage.concat(String.valueOf("\r\n")).concat(nextMsg);
            }
        }

        return new EException(
                shortMessage == null ? ex.toString() : shortMessage
                , detailMessage == null ? ex.toString() : detailMessage
                , ex == null ? null : ex.getCause());
    }

    /**
     * 创建以<code>,</code>分隔<code>size</code>个<code>?</code>的字符串。
     * 用于SQL预提交语句参数值的占位符部分。
     *
     * @param size <code>?</code>个数。
     * @return 以<code>?</code>开始，后续<code>,</code>和<code>?</code>交替出现的
     * 字符串缓冲。
     * @since 2.1
     */
    public static char[] createQuestionMarks(int size) {
        if (size < 1)
            throw new IllegalArgumentException("size < 1");
        char[] bf = new char[(size << 1) - 1];
        for (int i = 0; i < size; i++) {
            bf[(i << 1)] = '?';
            if (i < size - 1)
                bf[(i << 1) + 1] = ',';
        }
        return bf;
    }

    /**
     * 已知SQL字段列表，计算字段个数
     * 各个字段是以逗号（,）分隔的。单引号、双引号、圆括号（()）之内的逗号不是用来分隔字段的
     *
     * @param fieldList 由逗号分隔的字段列表字符串
     * @return 字段个数。如果fieldList为空字符串，则返回0，否则为逗号个数 + 1
     */
    public static int countField(String fieldList) {
        char[] chrs = fieldList.toCharArray();
        if (chrs.length == 0)
            return 0;
        int cnt = 0;//字段个数
        boolean notInQuatation = true;//不在单引号内
        boolean notInDblQuatation = true;//不在双引号内
        int parenthesis = 0;//在括号内
        int chr;
        for (int i = 1; i < chrs.length; i++) {
            chr = chrs[i];
            if (chr == '\'' && notInDblQuatation)
                //不在双引号内的单引号
                notInQuatation = !notInQuatation;
            else if (chr == '"' && notInQuatation)
                //不在单引号内的双引号
                notInDblQuatation = !notInDblQuatation;
            else if (notInQuatation && notInDblQuatation) {
                if (chr == ',' && parenthesis == 0)
                    //不在引号和括号内的逗号才断定是分隔字段的
                    cnt++;
                else if (chr == '(')
                    parenthesis++;
                else if (chr == ')')
                    parenthesis--;
            }
        }
        return cnt + 1;
    }

    /**
     * 给定字段值，构建该字段值上的查询条件
     *
     * @param value 字段值
     * @return (value = = null | | value.length () == 0)?
     * " IS NULL":"='".concat(value).concat("'");
     */
    public static String createIfNullFilter(String value) {
        return (value == null || value.length() == 0) ?
                " IS NULL" : "='".concat(value).concat(String.valueOf('\''));
    }

    /**
     * 构造指定字段列表上的插入语句，字段的值都是?，插入语句的执行由
     * {@link #prepareUpdate(String, Object[], boolean)}完成。
     *
     * @param table     实体表名，或带有INSTEAD OF 触发器的视图名
     * @param fieldList 符合SQL语法的字段列表字符串
     * @return 完整的插入语句，如：INSERT INTO table(fieldList)VALUES(?,?,...)
     * @see #createQuestionMarks
     * @see #countField
     */
    public static String prepareInsertStmt(String table, String fieldList) {
        return new StringBuffer("INSERT INTO ").append(table)
                .append('(')
                .append(fieldList)
                .append(")VALUES(")
                .append(createQuestionMarks(countField(fieldList)))
                .append(')')
                .toString();
    }

    /**
     * 构造指定字段列表上的插入语句，字段的值都是?，插入语句的执行由
     * {@link #prepareUpdate(String, Object[], boolean)}完成。
     *
     * @param table  实体表名，或带有INSTEAD OF 触发器的视图名
     * @param fields 字段名字符串数组。数组长度应该大于0。
     * @return 完整的插入语句，如：INSERT INTO table(filedList)VALUES(?,?,...)
     * @see #createQuestionMarks
     */
    public static String prepareInsertStmt(String table, String[] fields) {
        StringBuffer sql = new StringBuffer("INSERT INTO ").append(table)
                .append('(');
        int cntField = fields.length;
        for (int i = 0; i < cntField; i++) {
            if (i > 0)
                sql.append(',');
            sql.append(fields[i]);
        }
        return sql.append(")VALUES(")
                .append(createQuestionMarks(cntField))
                .append(')')
                .toString();
    }
    //	/** The charaters of String <code>)VALUES(</code>. */
    //	static char[] values = new char[]{')', 'V', 'A', 'L', 'U', 'E', 'S', '('};

    /**
     * 构造指定字段列表上的修改语句，修改字段的值和筛选条件中字段的值都是?。构造好的语句的执行由
     * {@link #prepareUpdate(String, Object[], boolean)}完成。
     *
     * @param table            实体表名，或带有INSTEAD OF 触发器的视图名
     * @param updatedFields    待修改的字段列表。如果待修改的字段只有一个，则该参数为字段名字符串；
     *                         如果待修改的字段的个数大于1，则该参数为这些字段的字符串数组。
     * @param primaryKeyFields 确定修改记录的关键字段，如果为单个字段，则为该字段字符串，
     *                         如果为多个字段，则为各字段字符串组成的字符串数组。如果不需要确定修改的记录，
     *                         即修改的是整张实体表中的记录，则置该参数值为null。
     * @return 完整的修改语句。如：UPDATE table SET f1=?,f2=?,f3=? WHERE p1=? AND p2=?
     */
    public static String prepareUpdateStmt(String table
            , Object updatedFields
            , Object primaryKeyFields) {
        StringBuffer sql = new StringBuffer("UPDATE ").append(table)
                .append(" SET ");
        //添加字段修改部分
        appendEqual(sql, updatedFields, false);
        //构造where语句部分
        if (primaryKeyFields != null) {
            sql.append(" WHERE ");
            appendEqual(sql, primaryKeyFields, true);
        }

        return sql.toString();
    }

    /**
     * 构造指定字段列表上的删除语句，筛选条件中字段的值都是?。构造好的语句的执行由
     * {@link #prepareUpdate(String, Object[], boolean)}完成。
     *
     * @param table            实体表名，或带有INSTEAD OF 触发器的视图名
     * @param primaryKeyFields 确定修改记录的关键字段，如果为单个字段，则为该字段字符串，
     *                         如果为多个字段，则为各字段字符串组成的字符串数组。如果不需要确定修改的记录，
     *                         即修改的是整张实体表中的记录，则置该参数值为null。
     * @return 完整的删除语句。如：DELETE FROM table WHERE p1=? AND p2=?
     */
    public static String prepareDeleteStmt(String table, Object primaryKeyFields) {
        StringBuffer sql = new StringBuffer("DELETE FROM ").append(table);
        //构造where语句部分
        if (primaryKeyFields != null) {
            sql.append(" WHERE ");
            appendEqual(sql, primaryKeyFields, true);
        }

        return sql.toString();
    }

    /**
     * 将指定的一个或多个字段以 <code>字段[i]=?delim字段[i-1]=?</code> 的形式附加指定的SQL语句后面
     *
     * @param sql        存储SQL语句的字符串缓冲
     * @param fields     单个字段字符串后者多个字段组成的字符串数组
     * @param delimIsAnd 分隔各字段部分是否为字符串<code>" AND "</code>，如果不是，则就是字符<code>,</code>。
     */
    public static void appendEqual(StringBuffer sql, Object fields, boolean delimIsAnd) {
        String[] fieldArray;
        if (fields instanceof String[]) {
            fieldArray = (String[]) fields;
            for (int i = 0; i < fieldArray.length; i++) {
                if (i > 0) {
                    if (delimIsAnd)
                        sql.append(" AND ");
                    else
                        sql.append(',');
                }
                sql.append(fieldArray[i]).append("=?");
            }
        } else
            sql.append(fields).append("=?");
    }
}
